﻿using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;

namespace HttpWebServer
{
    public sealed class ClientSide
    {
        private TcpClient _tcpClient;
        private string _request;
        private string _extension;
        private string _contentType;
        private byte[] _buffer;
        public event EventHandler<MessageEventArgs> RequestMessageEvent;
        public event EventHandler<MessageEventArgs> ResponseMessageEvent;

        public ClientSide()
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method ClientSide initialized");
            _request = "";
            _extension = "";
            _contentType = "";
        }

        ~ClientSide()
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method ~ClientSide initialized");
            ShutDownClientSide();
        }

        public void StartClientSide(TcpClient client, int bufferSize)
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method StartClientSide initialized");
            _buffer = new byte[bufferSize];
            _tcpClient = client;
            GetClientRequest();
            if (ProccessClientRequest() != 0)
            {
                SendErrorPage(404);
            }
            if (!File.Exists(_request.TrimStart('/')))
            {
                SendErrorPage(404);
            }
            GetFileExtension();
            OpenFile(_request.TrimStart('/'));
            ShutDownClientSide();
        }

        private void ShutDownClientSide()
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method ShutDownClientSide initialized");
            if (_tcpClient == null) return;
            _tcpClient.Close();
            _tcpClient = null;
        }

        private void GetClientRequest()
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method GetClientRequest initialized");
            int count;
            while ((count = _tcpClient.GetStream().Read(_buffer, 0, _buffer.Length)) > 0)
            {
                _request += Encoding.ASCII.GetString(_buffer, 0, count);
                if (_request.IndexOf("\r\n\r\n") < 0 && _request.Length <= 4096) continue;
                Trace.WriteLine(DateTime.Now + "-->" + "Bad request detected");
                break;
            }
            OnRequestMessageEvent(new MessageEventArgs("\nREQUEST\n" + _request));
        }

        private int ProccessClientRequest()
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method ProccessClientRequest initialized");
            var requestMatch = Regex.Match(_request, @"^\w+\s+([^\s\?]+)[^\s]*\s+HTTP/.*|");
            if (requestMatch == Match.Empty)
            {
                SendErrorPage(400);
                return -1;
            }
            _request = requestMatch.Groups[1].Value;
            _request = Uri.UnescapeDataString(_request);
            if (_request.IndexOf("..") >= 0)
            {
                SendErrorPage(400);
                return -1;
            }
            if (_request.EndsWith("/"))
            {
                _request += "index.html";
            }
            Trace.WriteLine(DateTime.Now + "-->" + "Request proccessed: " + _request);
            return 0;
        }

        private void GetFileExtension()
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method GetFileExtension initialized");
            var i = _request.LastIndexOf('.');
            if (i >= 0)
            { 
                _extension = _request.Substring(i); 
            }
            switch (_extension)
            {
                case ".htm":
                case ".html":
                    _contentType = "text/html";
                    break;
                case ".css":
                    _contentType = "text/stylesheet";
                    break;
                case ".js":
                    _contentType = "text/javascript";
                    break;
                case ".jpg":
                    _contentType = "image/jpeg";
                    break;
                case ".jpeg":
                case ".png":
                case ".gif":
                    _contentType = "image/" + _extension.Substring(1);
                    break;
                default:
                    if (_extension.Length > 1)
                    {
                        _contentType = "application/" + _extension.Substring(1);
                    }
                    else
                    {
                        _contentType = "application/unknown";
                    }
                    break;
            }
            Trace.WriteLine(DateTime.Now + "-->" + "Content type recognized: " + _contentType);
        }

        private void OpenFile(string filePath)
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method OpenFile initialized");
            FileStream fileStream;
            try
            {
                fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                Trace.WriteLine(DateTime.Now + "-->" + "Opened file: " + filePath);
            }
            catch (Exception)
            {
                SendErrorPage(500);
                return;
            }
            var headers = "HTTP/1.1 200 OK\nContent-Type: " + 
                _contentType + 
                "\nContent-Length: " + 
                fileStream.Length + "\n\n";
            var headersBuffer = Encoding.ASCII.GetBytes(headers);
            Trace.WriteLine(DateTime.Now + "-->" + "Headers set: " + headers);
            _tcpClient.GetStream().Write(headersBuffer, 0, headersBuffer.Length);
            OnResponseMessageEvent(new MessageEventArgs("\nRESPONSE\n" + headers));
            while (fileStream.Position < fileStream.Length)
            {
                var count = fileStream.Read(_buffer, 0, _buffer.Length);
                _tcpClient.GetStream().Write(_buffer, 0, count);
            }
            Trace.WriteLine(DateTime.Now + "-->" + "Sent Buffer: " + BitConverter.ToString(_buffer));
            fileStream.Close();
        }

        private void SendErrorPage(int errorCode)
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method SendErrorPage initialized");
            var errorCodeString = errorCode + " " + ((HttpStatusCode)errorCode);
            var simpleHtmlPage = "<html><body>" + errorCodeString + "</body></html>";
            var header = "HTTP/1.1 " + 
                errorCodeString + 
                "\nContent-type: text/html\nContent-Length:" + 
                simpleHtmlPage.Length + "\n\n" + 
                simpleHtmlPage;
            Trace.WriteLine(DateTime.Now + "-->" + "Header set: " + header);
            var buffer = Encoding.ASCII.GetBytes(header);
            OnResponseMessageEvent(new MessageEventArgs("\nRESPONSE\n" + header));
            _tcpClient.GetStream().Write(buffer, 0, buffer.Length);
            _request = "";
            _extension = "";
            _contentType = "";
            //tcpClient.Close();
        }

        private void OnRequestMessageEvent(MessageEventArgs args)
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method OnRequestMessageEvent initialized");
            Trace.WriteLine(DateTime.Now + "-->" + "Message: " + args.Message);
            var handler = RequestMessageEvent;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        private void OnResponseMessageEvent(MessageEventArgs args)
        {
            Trace.WriteLine(DateTime.Now + "-->" + "Method OnResponseMessageEvent initialized");
            Trace.WriteLine(DateTime.Now + "-->" + "Message: " + args.Message);
            var handler = ResponseMessageEvent;
            if (handler != null)
            {
                handler(this, args);
            }
        }
    }
}
